/**
 * 
 */
package server.connection.listening;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.log4j.Logger;

import server.conf.Preferences;
import server.connection.SelectorAdapter;
import server.exception.ServerException;
import server.exception.ServerRuntimeException;
import server.log.Logging;

/**
 * Listenes on the port and accepts incoming connections.
 * Connectins are then forwarded via SelectorAdapter to an available SelectorThread. 
 * 
 * @author Kamil Nowak
 *
 */
public class MainSelectorThread implements Runnable {

	private ServerSocketChannel server;
	private Selector selector;
	
	private String host = Preferences.instance().getPreference("host");
	private int port = Integer.valueOf(Preferences.instance().getPreference("port"));
	
	private Logger logger = Logging.instance().getLogger("main_selector_thread");
	
	private AtomicLong id = new AtomicLong(0);
	
	public void run() {
		
		logger.debug("MainSelectorThread starts");
		
		try {
			while (true) {
				init();
				listen();
			}
		} catch (IOException e) {
			logger.error("",e);
			throw new ServerRuntimeException(e);
		}
		
	}
	
	private void init() throws IOException {
		logger.info("Initialization");
		server = ServerSocketChannel.open();
		server.configureBlocking(false);
		server.socket().bind(new InetSocketAddress(host,port),
							SelectorAdapter.instance().getMaxConnsNum());
		logger.debug("Opening connection host: " + host + " port: " + port);
		selector = Selector.open();
		
		server.register(selector,SelectionKey.OP_ACCEPT);
	}
	
	private void listen() {
		logger.debug("Listening");
		while (true) {
			try {
				selector.select();
				
				Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
				while (selectedKeys.hasNext()) {
					SelectionKey key = selectedKeys.next();
					selectedKeys.remove();
					
					logger.info("Processing key: " + key.toString());
					
					if (!key.isValid()) {  logger.debug("Key is not valid: " + key.toString()); continue; }
					
					if (key.isAcceptable()) accept(key);
				}
				
			} catch (IOException e) {
				logger.error(e);
			}
		}
	}

	private void accept(SelectionKey key) throws IOException {
		logger.info("Accept key");
		ServerSocketChannel channel = (ServerSocketChannel) key.channel();
		
		SocketChannelDTO socketChannelDto = new SocketChannelDTO( id.incrementAndGet(), 
															channel.accept() );
		logger.info("New channel: " + socketChannelDto.toString());
		
		try {
			SelectorAdapter.instance().addSocketChannel(socketChannelDto);
		} catch (ServerException e) {
			logger.error("",e);
			closeSocketChannel( socketChannelDto.getSocketChannel() );
			//key.cancel();
		} catch (RuntimeException e) {
			logger.error("",e);
			closeSocketChannel( socketChannelDto.getSocketChannel() );
			//key.cancel();
		}
	}

	private void closeSocketChannel(SocketChannel socketChannel) throws IOException {
		socketChannel.socket().close();
		socketChannel.close();
	}

}
